前幾天的文章中介紹了 multi provider 的效果。今天要來認真的看一下 Angular 是怎麼處理 multi provider 的。
在 @Compontnet 裝飾詞上加上 providers 陣列,經過 compiler 編譯之後,會由一個名字是 ɵɵProvidersFeature
的函式來處理傳入的 provider 物件,以下是 Day 8 文章內的 AppComponent 經過編譯後產生的 JavaScript 程式碼:
AppComponent.ɵcmp = _angular_core__WEBPACK_IMPORTED_MODULE_0__[
"ɵɵdefineComponent"
]({
type: AppComponent,
selectors: [["app-root"]],
features: [
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵProvidersFeature"]([
{
provide: MULTI_TOKEN,
useValue: "OK",
multi: true,
},
{
provide: MULTI_TOKEN,
useValue: "OOOOOK",
multi: true,
},
]),
],
decls: 1,
vars: 0,
template: function AppComponent_Template(rf, ctx) {
if (rf & 1) {
_angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵelement"](0, "app-new");
}
},
styles: [
"\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsImZpbGUiOiJzcmMvYXBwL2FwcC5jb21wb25lbnQuc2NzcyJ9 */",
],
});
↑ Block 1
在 Block 1 中可以清楚的看到 compile 後的程式碼寫著 ɵɵProvidersFeature
。
再來就可以開始爬了!
先看一下 ɵɵProvidersFeature
函式的實作:
export function ɵɵProvidersFeature<T>(providers: Provider[], viewProviders: Provider[] = []) {
return (definition: DirectiveDef<T>) => {
definition.providersResolver =
(def: DirectiveDef<T>, processProvidersFn?: ProcessProvidersFunction) => {
return providersResolver(
def, //
processProvidersFn ? processProvidersFn(providers) : providers, //
viewProviders);
};
};
}
↑ Block 2
這個函式基本上就是會回傳另一個 function,並運用到了 JavaScript Closure 的概念(對 Closure 沒概念的話可以看一下這篇),把從 @Component 取得 providers 與 viewProviders 包在回傳的 function 內。
當 Angular 透過 ɵɵdefineComponent 函式來建立 component 的 definition object 時,這個 ɵɵProvidersFeature 回傳的函式就會被呼叫,同時會將 providersResolver
函式 assign 給這個 definition object 的 providersResolver 屬性。
providersResolver
函式的實作內容:
export function providersResolver<T>(
def: DirectiveDef<T>, providers: Provider[], viewProviders: Provider[]): void {
const tView = getTView();
if (tView.firstCreatePass) {
const isComponent = isComponentDef(def);
// The list of view providers is processed first, and the flags are updated
resolveProvider(viewProviders, tView.data, tView.blueprint, isComponent, true);
// Then, the list of providers is processed, and the flags are updated
resolveProvider(providers, tView.data, tView.blueprint, isComponent, false);
}
}
↑ Block 3
分開就 viewProviders 與 providers 使用 resolveProvider
函式做處理,來看一下 resolveProvider
的實作內容:
function resolveProvider(
provider: Provider, tInjectables: TData, lInjectablesBlueprint: NodeInjectorFactory[],
isComponent: boolean, isViewProvider: boolean): void {
provider = resolveForwardRef(provider);
if (Array.isArray(provider)) {
// Recursively call `resolveProvider`
// Recursion is OK in this case because this code will not be in hot-path once we implement
// cloning of the initial state.
for (let i = 0; i < provider.length; i++) {
resolveProvider(
provider[i], tInjectables, lInjectablesBlueprint, isComponent, isViewProvider);
}
} else {
const tView = getTView();
const lView = getLView();
let token: any = isTypeProvider(provider) ? provider : resolveForwardRef(provider.provide);
let providerFactory: () => any = providerToFactory(provider);
const tNode = getPreviousOrParentTNode();
const beginIndex = tNode.providerIndexes & TNodeProviderIndexes.ProvidersStartIndexMask;
const endIndex = tNode.directiveStart;
const cptViewProvidersCount =
tNode.providerIndexes >> TNodeProviderIndexes.CptViewProvidersCountShift;
if (isTypeProvider(provider) || !provider.multi) {
// ... 略
} else {
// ... 略
const existingProvidersFactoryIndex =
indexOf(token, tInjectables, beginIndex + cptViewProvidersCount, endIndex);
const existingViewProvidersFactoryIndex =
indexOf(token, tInjectables, beginIndex, beginIndex + cptViewProvidersCount);
const doesProvidersFactoryExist = existingProvidersFactoryIndex >= 0 &&
lInjectablesBlueprint[existingProvidersFactoryIndex];
const doesViewProvidersFactoryExist = existingViewProvidersFactoryIndex >= 0 &&
lInjectablesBlueprint[existingViewProvidersFactoryIndex];
if (isViewProvider && !doesViewProvidersFactoryExist ||
!isViewProvider && !doesProvidersFactoryExist) {
// 下接 Block 5
} else {
// 下接 Block 8
}
if (!isViewProvider && isComponent && doesViewProvidersFactoryExist) {
lInjectablesBlueprint[existingViewProvidersFactoryIndex].componentProviders!++;
}
}
}
}
↑ Block 4
當 Angular 發現現在在解析的 provider 沒有出現在 lInjectables、也沒有對應的 factory 就會進到 Block 5:
diPublicInInjector(
getOrCreateNodeInjectorForNode(
tNode as TElementNode | TContainerNode | TElementContainerNode, lView),
tView, token);
const factory = multiFactory(
isViewProvider ? multiViewProvidersFactoryResolver : multiProvidersFactoryResolver,
lInjectablesBlueprint.length, isViewProvider, isComponent, providerFactory);
if (!isViewProvider && doesViewProvidersFactoryExist) {
lInjectablesBlueprint[existingViewProvidersFactoryIndex].providerFactory = factory;
}
registerDestroyHooksIfSupported(tView, provider, tInjectables.length, 0);
tInjectables.push(token);
tNode.directiveStart++;
tNode.directiveEnd++;
if (isViewProvider) {
tNode.providerIndexes += TNodeProviderIndexes.CptViewProvidersCountShifter;
}
lInjectablesBlueprint.push(factory);
lView.push(factory);
↑ Block 5
Block 5 主要會先透過 diPublicInInjector 將 token 放入 bloom filter 內,並透過 multiFactory 函式產生一個 NodeInjectoryFactory 物件。
multiFactory 的第一個傳入參數就是日後 Angular 實際用來產生實體的工廠方法。
以這邊的例子來看,會使用 multiProvidersFactoryResolver 函式:
function multiProvidersFactoryResolver(
this: NodeInjectorFactory, _: undefined, tData: TData, lData: LView,
tNode: TDirectiveHostNode): any[] {
return multiResolve(this.multi!, []);
}
↑ Block 6
function multiResolve(factories: Array<() => any>, result: any[]): any[] {
for (let i = 0; i < factories.length; i++) {
const factory = factories[i]! as () => null;
result.push(factory());
}
return result;
}
↑ Block 7:真正用來回傳實體的函式 multiResolve
如果 Angular 發現要解析的 provider 已經存在於 lInjectables 的話,就會直接使用 multiFactoryAdd 這個函式將傳入的 provider 放入已存在的 NodeInjectFactory 內,如 Block 8:
const indexInFactory = multiFactoryAdd(
lInjectablesBlueprint!
[isViewProvider ? existingViewProvidersFactoryIndex :
existingProvidersFactoryIndex],
providerFactory, !isViewProvider && isComponent);
registerDestroyHooksIfSupported(
tView, provider,
existingProvidersFactoryIndex > -1 ? existingProvidersFactoryIndex :
existingViewProvidersFactoryIndex,
indexInFactory);
↑ Block 8
以上就是從宣告 multi provider → 放入 injector 一路到解析出實體的相關程式碼!
以下按照入團順序列出我們團隊夥伴的系列文章!